/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input;


import org.lex.input.effects.MinMaxCurrent;

import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;

public class RotationSphere {
	
	private Vector3f worldUp;
	private Vector3f worldLeft;
	
	private Vector3f direction;
	
	private Vector3f onSphere;
	private Quaternion vRot = new Quaternion();
	private Quaternion hRot = new Quaternion();
	private Matrix3f rotMatrix = new Matrix3f();
	
	/** temprory vector */
	private Vector3f tempVec = new Vector3f();
	
	/**
	 * defines an angle (in degrees) between the worldUp vector and
	 * onSphere vector
	 */
	private MinMaxCurrent vertical;
	
	/**
	 * defines an angle (in degrees) between the (-1)*direction vector
	 * and onSphere vector
	 */
	private MinMaxCurrent horizontal;
	
	/**
	 * the length of the onSphere vector 
	 */
	private MinMaxCurrent distance;
	
	public RotationSphere() {
		this(100, 45, 0,
				Vector3f.UNIT_Z.negate(),
				Vector3f.UNIT_Y,
				Vector3f.UNIT_X.negate());
	}
	
	/**
	 * All the vectors must be unit vectors, or some functions will not
	 * behave correctly.
	 * 
	 * @param vAngle the vertical angle, 90 degrees = above
	 * @param hAngle the horizontal angle, 0 degrees = behind
	 * @param dist the radius of the sphere 
	 * @param direction the vector to be be rotated by the specified angles
	 * @param worldUp the axis for horizontal rotation
	 * @param worldLeft the axis for vertical rotation
	 */
	public RotationSphere(float dist, float vAngle, float hAngle,
			Vector3f direction, Vector3f worldUp, Vector3f worldLeft)
	{
		this.direction = direction.normalize();
		this.worldUp = worldUp.normalize();
		this.worldLeft = worldLeft.normalize();
		
		vertical = new MinMaxCurrent(0, 89.9f, vAngle, false);
		horizontal = new MinMaxCurrent(0, 360, hAngle, true);
		distance = new MinMaxCurrent(25, 200, dist, false);
	}
	
	public boolean rotateVertical(float degrees) {
		return vertical.addToCurrent(degrees);
	}
	
	public boolean rotateHorizontal(float degrees) {
		return horizontal.addToCurrent(degrees);
	}
	
	public boolean zoomOut(float dist) {
		return distance.addToCurrent(dist);
	}
	
	public Vector3f findOnSphereLocation() {
		vRot.fromAngleNormalAxis(
				(180 - vertical.getCurrent())*FastMath.DEG_TO_RAD,
				worldLeft);
		
		hRot.fromAngleAxis(
				horizontal.getCurrent()*FastMath.DEG_TO_RAD,
				worldUp);
		
		Quaternion finalRot = hRot.multLocal(vRot);
		finalRot.toRotationMatrix(rotMatrix);
		
		Vector3f actualDir = rotMatrix.mult(direction, onSphere);
		
		Vector3f onSphere = actualDir
								.negateLocal()
								.normalizeLocal()
								.multLocal(distance.getCurrent());
		
		return onSphere;
	}

	/**
	 * @return the worldUp
	 */
	public Vector3f getWorldUp() {
		return worldUp;
	}
	
	public Vector3f getWorldLeft() {
		return worldLeft;
	}
	
	public void setVectors(Vector3f direction,
						   Vector3f worldUp,
						   Vector3f worldLeft)
	{
		this.direction = direction.normalize();
		this.worldUp = worldUp.normalize();
		this.worldLeft = worldLeft.normalize();
	}
	
	public MinMaxCurrent getVertical() {
		return vertical;
	}
	
	public MinMaxCurrent getHorizontal() {
		return horizontal;
	}
	
	public MinMaxCurrent getZoom() {
		return distance;
	}
	
	public float computeHorizontalAngle(Quaternion rot) {
		rot.toRotationMatrix(rotMatrix);
		Vector3f objectDirection = tempVec.set(direction);
		rotMatrix.multLocal(objectDirection);
		
		return computeHorizontalAngle(objectDirection);
	}
	
	/**
	 * Projects the vector onto the plane spanned by worldLeft and direction
	 * and then finds the counterclockwise rotation angle relative to
	 * direction vector.
	 * Will return 0 if the vector is perpendicular to the plane.
	 * 
	 * @param vec
	 * @return
	 */
	public float computeHorizontalAngle(Vector3f objectDirection) {
		float x = worldLeft.dot(objectDirection);
		float y = direction.dot(objectDirection);
		float length = FastMath.sqrt(x*x + y*y);
		
		if (length < FastMath.ZERO_TOLERANCE) return 0;
		
		y = y/length;
		float angle = ((float) Math.acos(y))*FastMath.RAD_TO_DEG;
		
		return (x < 0 ? 180 - angle : 180 + angle);
	}
}
